/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class CryptoSha256Const {
    public static final int NUM_512 = 512;
    public static final long HEX_100000000 = 0x100000000L;
    public static final long HEX_10000000000 = 0x10000000000L;
    public static final int NUM_8 = 8;
    public static final int NUM_64 = 64;
    public static final int NUM_2 = 2;
    public static final int NUM_3 = 3;
    public static final long NUM_9007199254740991 = 9007199254740991L;
    public static final int NUM_511 = 511;
    public static final int NUM_16 = 16;
    public static final int NUM_15 = 15;
    public static final int NUM_4 = 4;
    public static final int NUM_5 = 5;
    public static final int NUM_6 = 6;
    public static final int NUM_7 = 7;
    public static final int NUM_14 = 14;
    public static final int NUM_18 = 18;
    public static final int NUM_25 = 25;
    public static final int NUM_9 = 9;
    public static final int NUM_17 = 17;
    public static final int NUM_19 = 19;
    public static final int NUM_10 = 10;
    public static final int NUM_13 = 13;
    public static final int NUM_11 = 11;
    public static final int NUM_26 = 26;
    public static final int NUM_21 = 21;
    public static final int NUM_22 = 22;
    public static final int NUM_30 = 30;
    public static final int NUM_32 = 32;
    public static final int HEX_80000000 = 0x80000000;
    public static final int HEX_FFFFFFFF = 0xffffffff;
    public static final int NUM_31 = 31;
    public static final long HEX_F00000000000 = 0xf00000000000L;
    public static final int INT32MAX = 2147483647;
    public static final int INT32MIN = -2147483648;
    public static final long UINT32MAX = 4294967295L;
    public static final int NUM_1000 = 1000000;
    public static final int NUM_120 = 20;
    public static final int NUM_4500 = 4500;
    public static final int NUM_500 = 500;
}
/** @fileOverview Javascript SHA-256 implementation.
 *
 * An older version of this implementation is available in the public
 * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh,
 * Stanford University 2008-2010 and BSD-licensed for liability
 * reasons.
 *
 * Special thanks to Aldo Cortesi for pointing out several bugs in
 * this code.
 *
 * @author Emily Stark
 * @author Mike Hamburg
 * @author Dan Boneh
 */
class Sha256 {
    /**
     * The SHA-256 initialization vector, to be precomputed.
     * @private
     */
    public int[] init = new int[0]; 
  /*
       init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],
   */

    /**
     * The SHA-256 hash key, to be precomputed.
     * @private
     */
    public int[] _key = new int[0];
  /*
     _key:
     [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
     0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
     0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
     0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
     0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
     0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
     */

    /**
     * The hash's block size, in bits.
     * @constant
     */
    public int blockSize = CryptoSha256Const.NUM_512;

    public int[] _h = new int[0];

    public ArrayList<Long> _buffer = new ArrayList<>();

    public int _length = 0;

    /**
     * Hash a string or an array of words.
     * @static
     * @param {bitArray|String} data the data to hash.
     * @return {bitArray} The hash value, an array of 16 big-endian words.
     */
    public static List<Long> hash(Object data) {
        return new Sha256(null).update(data).finalizes();
    }
    /**
     * Context for a SHA-256 operation in progress.
     * @constructor
     */
    Sha256(Sha256 hash){
        if (this._key.length == 0) {
            this._precompute();
        }
        if (hash != null) {
            this._h = (Arrays.stream(hash._h).boxed().collect(Collectors.toCollection(ArrayList::new)).subList(0, hash._h.length - 1)).stream().mapToInt(Integer::intValue).toArray();
            this._buffer = (ArrayList<Long>) hash._buffer.subList(0, hash._buffer.size() - 1);
            this._length = hash._length;
        } else {
            this.reset();
        }
    }

    public int frac(double x) {
        long a = (long)((x - Math.floor(x)) * CryptoSha256Const.HEX_100000000) | 0;
        a = StanfordCryptoSha256.initUInt32(a);
        return (int) a;
    }
    /**
     * Function to precompute _init and _key.
     * @private
     */
    private void  _precompute() {
        int i = 0;
        int prime = 2;
        boolean isPrime;
        this.init = new int[CryptoSha256Const.NUM_8];
        this._key = new int[CryptoSha256Const.NUM_64];
        while (i < CryptoSha256Const.NUM_64) {
            isPrime = true;
            int factor = CryptoSha256Const.NUM_2;
            while (factor * factor <= prime) {
                if (prime % factor == 0) {
                    isPrime = false;
                    break;
                }
                factor += 1;
            }
            if (isPrime) {
                if (i < CryptoSha256Const.NUM_8) {
                    this.init[i] = this.frac(Math.pow(prime, (double) 1 / CryptoSha256Const.NUM_2));
                }
                this._key[i] = this.frac(Math.pow(prime, (double) 1 / CryptoSha256Const.NUM_3));
                i += 1;
            }
            prime += 1;
        }
    }
    /**
     * Reset the hash state.
     * @return this
     */
    public Sha256 reset() {
        this._h = this.init;
        this._buffer = new ArrayList<>();
        this._length = 0;
        return this;
    }
    /**
     * Input several words to the hash.
     * @param {bitArray|String} data the data to hash.
     * @return this
     */
    public Sha256 update(Object datas) {
        ArrayList<Long> data = new ArrayList<>();
        if (datas instanceof String) {
            data = Utf8String.toBits((String) datas);
        } else {
            data = (ArrayList<Long>) datas;
        }
        int i = 0;
        this._buffer = Arrays.stream(BitArray.concat(this._buffer.stream().toArray(Long[]::new), data.stream().toArray(Long[]::new))).collect(Collectors.toCollection(ArrayList::new));
        ArrayList<Long> b = this._buffer;
        int ol = this._length;
        this._length = this._length + BitArray.bitLength(data);
        int nl = this._length;
        if (nl > CryptoSha256Const.NUM_9007199254740991) {
            throw new Error("INVALID Cannot hash more than 2^53 - 1 bits");
        }
        ArrayList<Long> c = new ArrayList<>();
        for (int j = 0; j < b.size(); j++) {
            c.add((long)StanfordCryptoSha256.initUInt32(b.get(j)));
        }
        int j = 0;
        i = CryptoSha256Const.NUM_512 + ol - ((CryptoSha256Const.NUM_512 + ol) & CryptoSha256Const.NUM_511);
        while (i <= nl) {
            this._block((ArrayList<Long>) c.subList(CryptoSha256Const.NUM_16 * j, CryptoSha256Const.NUM_16 * (j + 1)));
            j += 1;
            i += CryptoSha256Const.NUM_512;
        }
        b.removeAll(b.subList(0, CryptoSha256Const.NUM_16 * j));
        return this;
    }
    /**
     * Complete hashing and output the hash value.
     * @return {bitArray} The hash value, an array of 8 big-endian words.
     */
    public List<Long> finalizes(){
        // Round out and push the buffer
        this._buffer = Arrays.stream(BitArray.concat(this._buffer.stream().toArray(Long[]::new), Collections.nCopies(1, BitArray.partial(1, 1)).stream().toArray(Long[]::new))).collect(Collectors.toCollection(ArrayList::new));

        // Round out the buffer to a multiple of 16 words, less the 2 length words.
        int i = this._buffer.size() + CryptoSha256Const.NUM_2;
        while ((i & CryptoSha256Const.NUM_15) != 0) {
            this._buffer.add(0L);
            i += 1;
        }
        // append the length
        long a = (this._length / CryptoSha256Const.HEX_100000000) | 0;
        this._buffer.add(a);
        this._buffer.add((long)this._length);

        while (!(this._buffer.size() == 0)) {
            ArrayList<Long> chunk = new ArrayList<>(this._buffer.subList(0, CryptoSha256Const.NUM_16));
            this._buffer.removeAll(this._buffer.subList(0, CryptoSha256Const.NUM_16));
            this._block(chunk);
        }
        this.reset();

        return Arrays.stream(Arrays.copyOf(Arrays.stream(this._h).asLongStream().toArray(), this._h.length)).boxed().collect(Collectors.toList());
    }
    /**
     * Perform one cycle of SHA-256.
     * @param {Uint32Array|bitArray} w one block of words.
     * @private
     */
    private void  _block(ArrayList<Long> input) {
        ArrayList<Long> w = input;
        long tmp;
        int a;
        int b;
        int[] k = this._key;
        int h0 = this._h[0];
        int h1 = this._h[1];
        int h2 = this._h[CryptoSha256Const.NUM_2];
        int h3 = this._h[CryptoSha256Const.NUM_3];
        int h4 = this._h[CryptoSha256Const.NUM_4];
        int h5 = this._h[CryptoSha256Const.NUM_5];
        int h6 = this._h[CryptoSha256Const.NUM_6];
        int h7 = this._h[CryptoSha256Const.NUM_7];
        for (int i = 0; i < CryptoSha256Const.NUM_64; i++) {
            if (i < CryptoSha256Const.NUM_16) {
                tmp = w.get(i);
            } else {
                a = (int) StanfordCryptoSha256.initUInt32(w.get((i + 1) & CryptoSha256Const.NUM_15));
                b = (int) StanfordCryptoSha256.initUInt32(w.get((i + CryptoSha256Const.NUM_14) & CryptoSha256Const.NUM_15));
                long value1 = (a >>> CryptoSha256Const.NUM_7) ^ (a >>> CryptoSha256Const.NUM_18) ^ (a >>> CryptoSha256Const.NUM_3) ^ (a << CryptoSha256Const.NUM_25) ^ (a << CryptoSha256Const.NUM_14);
                long value2 = (b >>> CryptoSha256Const.NUM_17) ^ (b >>> CryptoSha256Const.NUM_19) ^ (b >>> CryptoSha256Const.NUM_10) ^ (b << CryptoSha256Const.NUM_15) ^ (b << CryptoSha256Const.NUM_13);
                long value3 = w.get(i & CryptoSha256Const.NUM_15) + w.get((i + CryptoSha256Const.NUM_9) & CryptoSha256Const.NUM_15);
                w.set(i & CryptoSha256Const.NUM_15, (long)StanfordCryptoSha256.initUInt32(value1 + value2 + value3));
                tmp = w.get(i & CryptoSha256Const.NUM_15);
            }
            h4 = StanfordCryptoSha256.initUInt32(h4);
            long value = ((int)h4 >>> CryptoSha256Const.NUM_6) ^ ((int)h4 >>> CryptoSha256Const.NUM_11) ^ ((int)h4 >>> CryptoSha256Const.NUM_25) ^ (h4 << CryptoSha256Const.NUM_26) ^ (h4 << CryptoSha256Const.NUM_21) ^ (h4 << CryptoSha256Const.NUM_7);
            tmp = StanfordCryptoSha256.initUInt32(tmp + h7 + value + (h6 ^ (h4 & (h5 ^ h6))) + k[i]);
            h7 = StanfordCryptoSha256.initUInt32(h6);
            h6 = StanfordCryptoSha256.initUInt32(h5);
            h5 = StanfordCryptoSha256.initUInt32(h4);
            h4 = StanfordCryptoSha256.initUInt32(h3 + tmp);
            h3 = StanfordCryptoSha256.initUInt32(h2);
            h2 = StanfordCryptoSha256.initUInt32(h1);
            h1 = StanfordCryptoSha256.initUInt32(h0);
            h0 = StanfordCryptoSha256.initUInt32( tmp + ((h1 & h2) ^ (h3 & (h1 ^ h2))) + (((int)h1 >>> CryptoSha256Const.NUM_2) ^ ((int)h1 >>>CryptoSha256Const.NUM_13) ^ ((int)h1 >>> CryptoSha256Const.NUM_22) ^ (h1 << CryptoSha256Const.NUM_30) ^ (h1 << CryptoSha256Const.NUM_19) ^ (h1 << CryptoSha256Const.NUM_10)));
        }
        this._h[0] = StanfordCryptoSha256.initUInt32(this._h[0] + h0);
        this._h[1] = StanfordCryptoSha256.initUInt32(this._h[1] + h1);
        this._h[CryptoSha256Const.NUM_2] = StanfordCryptoSha256.initUInt32(this._h[CryptoSha256Const.NUM_2] + h2);
        this._h[CryptoSha256Const.NUM_3] = StanfordCryptoSha256.initUInt32(this._h[CryptoSha256Const.NUM_3] + h3);
        this._h[CryptoSha256Const.NUM_4] = StanfordCryptoSha256.initUInt32(this._h[CryptoSha256Const.NUM_4] + h4);
        this._h[CryptoSha256Const.NUM_5] = StanfordCryptoSha256.initUInt32(this._h[CryptoSha256Const.NUM_5] + h5);
        this._h[CryptoSha256Const.NUM_6] = StanfordCryptoSha256.initUInt32(this._h[CryptoSha256Const.NUM_6] + h6);
        this._h[CryptoSha256Const.NUM_7] = StanfordCryptoSha256.initUInt32(this._h[CryptoSha256Const.NUM_7] + h7);
        this.init = this._h;
    }
}

class Utf8String {
    public static ArrayList<Long> toBits(String str){
        ArrayList<Long> out = new ArrayList<>();
        int i = 0;
        long tmp = 0;
        while (i < str.length()) {
            tmp = (tmp << CryptoSha256Const.NUM_8) | str.codePointAt(i);
            if ((i & CryptoSha256Const.NUM_3) == CryptoSha256Const.NUM_3) {
                out.add(tmp);
                tmp = 0;
            }
            i += 1;
        }
        if ((i & CryptoSha256Const.NUM_3) != 0) {
            out.add(BitArray.partial(CryptoSha256Const.NUM_8 * (i & CryptoSha256Const.NUM_3), tmp));
        }
        return out;
    }
}
class BitArray {
    /**
     * Concatenate two bit arrays.
     * @param {bitArray} a1 The first array.
     * @param {bitArray} a2 The second array.
     * @return {bitArray} The concatenation of a1 and a2.
     */
    public static Long[] concat(Long[] a1, Long[] a2) {
        if (a1.length == 0 || a2.length == 0) {
            return Stream.of(a1, a2).flatMap(Arrays::stream).toArray(Long[]::new);
        }

        long last = a1[a1.length - 1];
        int shift = BitArray.getPartial(last);
        if (shift == CryptoSha256Const.NUM_32) {
            return Stream.of(a1, a2).flatMap(Arrays::stream).toArray(Long[]::new);
        } else {
            return BitArray.shiftRight(a2, shift, last | 0,  Arrays.asList(a1).subList(0, a1.length - 1));
        }
    }

    /**
     * Find the length of an array of bits.
     * @param {bitArray} a The array.
     * @return {Number} The length of a, in bits.
     */
    public static int bitLength(List<Long> a) {
        int l = a.size();
        Long x;
        if (l == 0) {
            return 0;
        }
        x = a.get(l - 1);
        return (int) ((l - 1) * CryptoSha256Const.NUM_32 + BitArray.getPartial(x));
    }

    /**
     * Truncate an array.
     * @param {bitArray} aa The array.
     * @param {Number} len1 The length to truncate to, in bits.
     * @return {bitArray} A new array, truncated to len bits.
     */
    public static ArrayList<Long> clamp(ArrayList<Long> a, int len) {
        ArrayList<Long> _a = a;
        int _len = len;
        if (_a.size() * CryptoSha256Const.NUM_32 < _len) {
            return _a;
        }

        int sliceLength = (int) Math.ceil(_len / CryptoSha256Const.NUM_32);
        _a = (ArrayList<Long>) _a.subList(0, sliceLength);

        int l = _a.size();
        _len = _len &  CryptoSha256Const.NUM_32;

        if (l > 0 && _len != 0) {
            _a.set(l-1, BitArray.partial(_len, _a.get(l-1) & (CryptoSha256Const.HEX_80000000 >> (_len - 1)), true));
        }

        return _a;
    }

    public static long partial(long len, long x) {
        return partial(len, x, false);
    }
    /**
     * Make a partial word for a bit array.
     * @param {Number} len The number of bits in the word.
     * @param {Number} x The bits.
     * @param {Number} [end=0] Pass 1 if x has already been shifted to the high side.
     * @return {Number} The partial word.
     */
    public static long partial(long len, long x, boolean end) {
        if (len == CryptoSha256Const.NUM_32) {
            return x;
        } else {
            long result = ((end ? x | 0 : StanfordCryptoSha256.initUInt32(x << (CryptoSha256Const.NUM_32 - len))) + len * CryptoSha256Const.HEX_10000000000);
            return result;
        }
    }

    /**
     * Get the number of bits used by a partial word.
     * @param {Number} x The partial word.
     * @return {Number} The number of bits used by the partial word.
     */
    public static int getPartial(long x) {
        return Math.round((float) x / CryptoSha256Const.HEX_10000000000) != 0 ? Math.round((float) x / CryptoSha256Const.HEX_10000000000) : CryptoSha256Const.NUM_32;
    }

    /** Shift an array right.
     * @param {bitArray} a The array to shift.
     * @param {Number} shift1 The number of bits to shift.
     * @param {Number} [carry1=0] A byte to carry in
     * @param {bitArray} [out1=[]] An array to prepend to the output.
     * @private
     */
    public static Long[] shiftRight(Long[] a, int shift, long carry, List<Long> out) {
        long last2 = 0;
        long shift2;
        List<Long> _out = out;
        long _carry = StanfordCryptoSha256.initUInt32(carry);
        int _shift = shift;
        if (_out.size() == 0) {
            _out = new ArrayList<>();
        }

        while (_shift >= CryptoSha256Const.NUM_32) {
            out.add(_carry);
            _carry = 0;
            _shift -= CryptoSha256Const.NUM_32;
        }

        if (_shift == 0) {
            return Stream.of(out.stream().toArray(Long[]::new), a).flatMap(Arrays::stream).toArray(Long[]::new);
        }

        for (int i = 0; i < a.length; i++) {
            _out.add(_carry | ((int) StanfordCryptoSha256.initUInt32(a[i]) >>> _shift));
            _carry = (a[i] << (CryptoSha256Const.NUM_32 - _shift)) & CryptoSha256Const.HEX_FFFFFFFF;
        }

        last2 = a.length == 0 ? 0 : a[a.length - 1];
        shift2 = BitArray.getPartial(last2) != 0 ? 1 : 0;

        _out.add(BitArray.partial((_shift + shift2) & CryptoSha256Const.NUM_31, (_shift + shift2) > CryptoSha256Const.NUM_32 ? _carry : _out.remove(_out.size() - 1), true));
        return _out.stream().toArray(Long[]::new);
    }
}
class Hex {
    /** Convert from a bitArray to a hex string. */
    public static String fromBits(List<Long> arr) {
        String out = "";
        for (int i = 0; i < arr.size(); i++) {
            long hexValue = (arr.get(i) | 0) + CryptoSha256Const.HEX_F00000000000;
            out += Long.toHexString(hexValue).substring(CryptoSha256Const.NUM_4);
        }
        String str = out.substring(0, BitArray.bitLength(arr) / CryptoSha256Const.NUM_4);
        return str;
    }
}

class StanfordCryptoSha256 {
    public static int initUInt32(long a){
        long tmp = a;
        if (tmp > CryptoSha256Const.INT32MAX) {
            long max = CryptoSha256Const.UINT32MAX;
            tmp = tmp % (max + 1);
            if (tmp > CryptoSha256Const.INT32MAX) {
                tmp = tmp - max - 1;
            }
        } else if (tmp < CryptoSha256Const.INT32MIN) {
            long max = CryptoSha256Const.UINT32MAX;
            tmp = tmp % (max + 1);
            if (tmp < CryptoSha256Const.INT32MIN) {
                tmp = tmp + max + 1;
            }
        }
        return (int) tmp;
    }
}
/*
 *  @State
 *  @Tags Jetstream2
 */
class Benchmark {
    public static void main(String[] args) {
        new Benchmark().runIteration();
    }

    /*
     *  @Benchmark
     */
    public void runIteration() {
        long startTime = System.nanoTime();
        for (int index = 0; index < CryptoSha256Const.NUM_120; index++) {
            List<Long> hash = Sha256.hash("b4d");
            for (int i = 0; i < CryptoSha256Const.NUM_4500; i++) {
                hash = Sha256.hash(hash);
                if (i % CryptoSha256Const.NUM_500 == 0) {
                    // debugLog("第" + (i + 1) + "条的hash: " + hash.toString());
                }
            }

            if (!Hex.fromBits(hash).equals("719043495be84b97fe4f5d7e61c99d6d1ba0cd6974a6b10c684c25a44ddd0c03")) {
                throw new Error("Bad result");
            }
        }
        long endTime = System.nanoTime();
        System.out.println("stanford-crypto-sha256: ms = " + (double)(endTime - startTime) / CryptoSha256Const.NUM_1000);
    }
    interface ArkTools {
        int timeInUs(int args);
    }

    public boolean isDebug = false;
    public void debugLog(String msg) {
        if (isDebug) {
            System.out.println(msg);
        }
    }
}

